package JET.Centers;

import JET.Centers.Graphics.GUICenter;
import JET.Animator;
import JET.Constants;
import JET.Frames.DataModels.DataModelsFactory;
import JET.geom.Rect2d;
import JET.geom.Vec2d;
import JET.RunMode;
import JET.Scene;
import JET.UtilPanels.UPSceneInspector;
import JET.UtilPanels.UPSceneItemsInspector;
import java.util.ArrayList;
import JET.physics.Mass;
import JET.physics.interfaces.ProjectTreeSceneFilterNode;
import JET.physics.interfaces.ProjectTreeNode;
import JET.recorder.Recorder;
import XMLStuff.XMLHelper2;
import java.awt.geom.Rectangle2D;
import java.io.File;
import java.io.FileOutputStream;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import javax.xml.transform.Transformer;

import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;


/**
 *
 * @author 057Ry
 */
public class ControlCenter implements Constants
{
    public RunMode runMode;
    public HashMap<String, Scene> scenes;
    
    // Pausing scenes
    final public ArrayList<Scene> pausedScenes = new ArrayList<Scene>();
    
    public HashMap<String, Animator> animators;

    public MouseCenter      MC;
    public GUICenter        GUIC;
    
    public ActionsCenter    AC;
    
    // zmienne do "sypania" masami
    public boolean  continous_ADD_MANY_MASS2D;
    public Thread   addManyMass2dThread;
    public Vec2d    addManyMass2dVec2d;
    
    // zmienne do przesuwania mass2d
    public boolean continous_MOVE_MANY_MASS2D;
    public Rect2d moveManyMass2dCatchArea;
    
    // zmienne ogolnego przeznaczenia
    public ArrayList<Mass> ctrlMass2dList;
    
    // zmienne do okreslania stanu animacji
    public boolean animationInProgress = false;

    // when using predefined objects creation tool, this is taken under consideration, wheather add object to actual or new spring body
    public boolean createNewSpringBody = true;

    // zmienne jakiestam :]
    private String projectName = "";
    private String projectFile = "";
    private File currentDirectory;

    // zmienne do globalForces
    //public double gravityValue = 1.0;
    
    // Animator stuff
    private int mainLoopSleepTime = 5;
    private int mainLoopNoRedrawSteps = 3;

    // Recording stuff
    private Recorder recorder;
    private boolean record=false;

    // Drawing optimization
    public boolean optimizeDrawing=false;

    public static ControlCenter controlCenter = null;

    // Threads pool for tree path selections
    public final Executor cachedThreadPoll = Executors.newCachedThreadPool();

    private ControlCenter(RunMode runMode) {
        this.runMode = runMode;

        scenes = new HashMap<String, Scene>();
        animators = new HashMap<String, Animator>();
    }

    /**
     * Initializes all objects that needs ControlCenter reference at the time of their
     * creation. So here should be inited all objects that CC must have, but they cannot be
     * initiated in CC constructor - as it would cause creation of many instances of CC instead of
     * just one!
     */
    public void postinitInit() {
        MC      = new MouseCenter();
        AC      = new ActionsCenter();
    }

    public void setGUIC(GUICenter guic) {
        this.GUIC = guic;
    }

    public static synchronized ControlCenter getInstance() {
        
        if (controlCenter==null) {
            controlCenter = new ControlCenter(RunMode.Desktop);
            controlCenter.postinitInit();
        }

        return controlCenter;
    }

////////////////////////////////////////////////////////////////////////////////
//                              A N I M A T O R                               //
////////////////////////////////////////////////////////////////////////////////

    public Animator getSceneAnimator(String sceneName) {
        return animators.get(sceneName);
    }
    public void setMainLoopNoRedrawSteps(int mainLoopNoDrawSimSteps) {
        this.mainLoopNoRedrawSteps = mainLoopNoDrawSimSteps;
    }
    public int getMainLoopNoRedrawSteps() {
        return mainLoopNoRedrawSteps;
    }
    public int getMainLoopSleepTime() {
        return mainLoopSleepTime;
    }
    public void setMainLoopSleepTime(int mainLoopSleepTime) {
        this.mainLoopSleepTime = mainLoopSleepTime;
    }

    /**
     * Begins to animate specified scene
     * @param sceneName Name of a scene to be animated.
     */
    public void play(String sceneName) {
        if (sceneName!=null && !"".equals(sceneName) && scenes.containsKey(sceneName)) {
            stop(sceneName);
            animators.remove(sceneName);
            Animator anim  = new Animator(sceneName);
            synchronized(anim) {
                animators.put(sceneName, anim);
                anim.start();
            }
        }
    }

    /**
     * Stops simulation/animation of specified scene.
     * @param sceneName Name of a scene that animation should be stopped.
     */
    public void stop(String sceneName) {
        if (sceneName!=null && !"".equals(sceneName) && scenes.containsKey(sceneName)) {
            Animator anim  = animators.get(sceneName);
            if (anim!=null) {
                synchronized(anim) {
                    anim.stopSimulation();
                }
            }
        }
    }

    public void pause(String sceneName) {
        pausedScenes.add(getScene(sceneName));
        stop(sceneName);
    }

    public void unpause(String sceneName, boolean forceRestart) {
        Scene s = getScene(sceneName);
        pausedScenes.remove(s);
        if (forceRestart || !isSceneAnimated(sceneName))
            play(sceneName);
    }

    public boolean isPaused(String sceneName) {
        return pausedScenes.contains(getScene(sceneName));
    }

    /**
     * Stops all scenes and stores information about which were running so that
     * later to restart previously running scenes you can call unpauseAll().
     */
    public void pauseAll() {
        pausedScenes.clear();
        
        Collection<Scene> sceneCol = Collections.synchronizedCollection(scenes.values());
        synchronized(sceneCol) {
            for(Scene s : sceneCol) {
                if (isSceneAnimated(s.getName())) {
                    stop(s.getName());
                    pausedScenes.add(s);
                }
            }
        }
    }

    /**
     * Unpauses previously paused scenes.
     * @param forceRestart If previously paused scenes were restarted since last pause by another call they will be restarted again if this param is true.
     */
    public void unpauseAll(boolean forceRestart) {
        for(Scene s : pausedScenes)
            if (forceRestart || !isSceneAnimated(s.getName()))
                play(s.getName());
        pausedScenes.clear();
    }

    /**
     * States if specified scene is currently animated.
     * @param sceneName Name of a scene to check.
     * 
     * @return True if scene is animated false otherwise.
     */
    public boolean isSceneAnimated(String sceneName) {
        Map<String, Animator> animCol = Collections.synchronizedMap(animators);
        synchronized(animCol) {
            Animator anim  = animCol.get(sceneName);
            if (anim!=null) {
                return anim.isAlive();
            }
        }
        
        return false;
    }

////////////////////////////////////////////////////////////////////////////////
//                              R E C O R D E R                               //
////////////////////////////////////////////////////////////////////////////////

    public boolean isRecord() {
        return record;
    }
    public void setRecord(boolean r) {
        record=r;
    }
    public Recorder getRecorder() {
        return recorder;
    }
    public void setRecorder(Recorder r) {
        this.recorder = r;
    }
    
////////////////////////////////////////////////////////////////////////////////
//                     P R O J E C T   M A N A G E M E N T                    //
////////////////////////////////////////////////////////////////////////////////

    /**
     * @return string that is just project name (not a full path to project file).
     */
    public String getProjectName() {
        return projectName;
    }
    /**
     * @return string that is full path to project file.
     */
    public String getProjectFile() {
        return projectFile;
    }

    public File getCurrentDirectory() {
        return currentDirectory;
    }

    public void setCurrentDirectory(File currentDirectory) {
        this.currentDirectory = currentDirectory;
    }

    public void setProjectName(String projectName) {
        this.projectName = projectName;
    }

    public void setProjectFile(String projectFile) {
        this.projectFile = projectFile;
        GUIC.setTitle(MAIN_FRAME_TITLE+" ("+projectFile+")");
    }

    public void addScene(Scene scene, String name) {
        scenes.put(name, scene);
        scene.setName(name);
    }

    public void addScene(Scene scene, String name, String path) {
        scenes.put(name, scene);
        scene.setName(name);

        String[] filters = path.split("/");
        scenesTreeNode.buildFilterPath(filters);
        ProjectTreeSceneFilterNode fNode = scenesTreeNode.getFilterNodeByNameDeep(filters[filters.length-1]);
        fNode.addScene(scene);
    }

    public Scene getCurrentScene() {
        if (GUIC!=null)
            return scenes.get(GUIC.getCurrentSceneName());
        else
            return null;
    }

    public Scene getScene(String sceneName) {
        return scenes.get(sceneName);
    }

    public Collection<Scene> getSceneListSync() {
        return Collections.synchronizedCollection(scenes.values());
    }

    public void renameScene(String sceneName, String newName) {
        Scene scene = scenes.get(sceneName);

        if (scene!=null) {
            scenes.remove(sceneName);
            scene.setName(newName);
            scenes.put(newName, scene);

            GUIC.renameSceneTab(sceneName, newName);
        }
    }

    public void removeScene(String sceneName) {
        Scene scene = getScene(sceneName);
        if (scene!=null) {

            // Remove scene from hashtable (linear storage place)
            scenes.remove(sceneName);

            // Remove scene from scene filters tree (tree like storage place)
            scenesTreeNode.getNodeOwningScene(scene).removeScene(scene);
        }
    }

    public String getSceneFilterPath(Scene scene) {
        return scenesTreeNode.getScenePath(scene);
    }

    //public void saveProjectFile(String filename) throws ParserConfigurationException, TransformerConfigurationException, TransformerException, FileNotFoundException, IOException {
        /*
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        DocumentBuilder db = dbf.newDocumentBuilder();
        Document doc = db.newDocument();

        //doc.appendChild( doc.createTextNode("\n"));
        
        Element projectElement = doc.createElement("project");
        projectElement.setAttribute("name", getProjectName());
        projectElement.setAttribute("app", "JET");

        projectElement.appendChild( doc.createTextNode("\n"));
        projectElement.appendChild( getEngine().getXMLRepresentation(doc) );

        projectElement.appendChild( doc.createTextNode("\n"));

        Element actualSBElement = doc.createElement("actual_spring_body");
        actualSBElement.appendChild( doc.createTextNode(""+getEngine().getSpringBodyList().indexOf(actualSpringBody)) );
        projectElement.appendChild(actualSBElement);

        projectElement.appendChild( doc.createTextNode("\n"));

        Element actionsEl = doc.createElement("actions");
        actionsEl.appendChild( doc.createTextNode("\n"));
        
        for(Action a : AC.getActionList())
            actionsEl.appendChild(a.getXMLRepresentation(doc, this));

        projectElement.appendChild(actionsEl);

        projectElement.appendChild( doc.createTextNode("\n"));

        projectElement.appendChild( vKeyboard.getXMLRepresentation(doc, this));

        projectElement.appendChild( doc.createTextNode("\n"));
        projectElement.appendChild( doc.createTextNode("\n"));

        GUIC.saveAllScripts();
        projectElement.appendChild( XMLHelper.getScriptsCenterElement(SC, doc));

        doc.appendChild(projectElement);
        // zapisywanie
        Transformer t = TransformerFactory.newInstance().newTransformer();

        FileOutputStream foStream = new FileOutputStream(filename);
        t.transform(new DOMSource(doc), new StreamResult(foStream));
        foStream.close();
        */
    //}

    public void saveProjectFile2(String filename) {
        GUIC.saveAllScripts();

        try {
            Transformer t = TransformerFactory.newInstance().newTransformer();
            FileOutputStream foStream = new FileOutputStream(filename);
            t.transform(new DOMSource(XMLHelper2.buildProjectElement()), new StreamResult(foStream));
            foStream.close();
        } catch (Exception ex) {ex.printStackTrace();}
    }

    public void loadProjectFile2(String path) throws Exception {
        if (!"".equals(path))
            XMLHelper2.loadProject(path);
    }

    public void closeCurrentProject() {
//        GUIC.closeAllScriptTabs();
//        GUIC.closeAllSceneTabs();
//        GUIC.removeAllSceneTabs();
//
//        for(Scene s : scenes.values()) {
//            stop(s.getName());
//            removeScene(s.getName());
//        }
//
//        GUIC.setActualScene("");

        for(Scene s : scenes.values()) stop(s.getName());
        GUIC.closeAllScriptTabs();
        GUIC.closeAllSceneTabs();
        GUIC.removeAllSceneTabs();

        this.scenes.clear();
        this.scenesTreeNode = new ScenesTreeNode();

        ((UPSceneInspector)GUIC.getUtilTab(GUICenter.UtilTabs.SceneInspector)).setScene(null);
        ((UPSceneInspector)GUIC.getUtilTab(GUICenter.UtilTabs.SceneInspector)).updateSceneTreeUI();

        ((UPSceneItemsInspector)GUIC.getUtilTab(GUICenter.UtilTabs.SceneItemsInspector)).setScene(null);
        ((UPSceneItemsInspector)GUIC.getUtilTab(GUICenter.UtilTabs.SceneItemsInspector)).updateSceneItemsTreeUI();

        DataModelsFactory.getProjectTreeModel().fireWholeTreeChanged();
        DataModelsFactory.getSceneTreeModel().fireWholeTreeChanged();
        DataModelsFactory.getSceneItemsTreeModel().fireWholeTreeChanged();
        DataModelsFactory.getTorquesListModel().fireTorquesListChanged();

        setProjectFile("");
        setProjectName("");
    }

    //public void loadProjectFile(String filename, boolean centerView) throws NotJETXMLProjectFileException, ParserConfigurationException, SAXException, IOException {
        /*
        //File file = new File(filename);

        //if ( !Utils.isJetProjectFile(filename) ) throw new NotJETXMLProjectFileException(file);
        
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        DocumentBuilder db = dbf.newDocumentBuilder();
        Document doc = db.parse(filename);

        // Element "project"
        Element projectElem = doc.getDocumentElement();

        NodeList children = projectElem.getChildNodes();
        //Engine newEngine=null;
        
        for(int i=0; i<children.getLength(); i++)
        {
            Node node = children.item(i);
            if (node instanceof Element)
            {
                Element elem = (Element)node;

                if (elem.getTagName().equals("scene")) {

                    Engine engine = new Engine();

                    // Parse scene attributes
                    NamedNodeMap attributes = elem.getAttributes();
                    for(int a=0; a<attributes.getLength(); a++) {
                        Node attr = attributes.item(a);
                        if (attr.getNodeName().equals("name")) {
                            engine.setName(attr.getTextContent());
                        }
                    }

                    // Store this engine
                    engines.put(engine.getName(), engine);

                    // Set new engine as current
                    setActiveEngine(engine.getName());

                    NodeList sceneList = elem.getChildNodes();
                    for(int s=0; s<sceneList.getLength(); s++) {
                        if (sceneList.item(s) instanceof Element) {
                            Element sceneElem = (Element)sceneList.item(s);

                            if (sceneElem.getTagName().equals("springBody"))
                                StaticActions.addSpringBodyToScene( XMLHelper.readSpringBody(sceneElem), false);
                            else if (sceneElem.getTagName().equals("torque"))
                                StaticActions.addTorque( new Torque(sceneElem, getEngine()));
                            else if (sceneElem.getTagName().equals("spring"))
                                StaticActions.addSpringToEngineList( XMLHelper.readSpring(sceneElem,getEngine()));
                            else if (sceneElem.getTagName().equals("rotary_spring"))
                                StaticActions.addRSpringToEngineList( new RSpring(sceneElem,getEngine()));
                            else if (sceneElem.getTagName().equals("materialNames"))
                                XMLControlCenterHelper.loadMaterialNames(sceneElem);
                            else if (sceneElem.getTagName().equals("materialPairs"))
                                XMLControlCenterHelper.loadMaterialPairs(sceneElem);
                        }
                    } // end of scene list iterations
                } // end of scene element
                else if (elem.getTagName().equals("actual_spring_body")) {
                    int sbIndex = XMLHelper.readInt(elem);
                    StaticActions.setActualSpringBody(sbIndex, true);
                }
                else if (elem.getTagName().equals("actions")) {
                    NodeList actionsList = elem.getChildNodes();

                    for(int a=0; a<actionsList.getLength(); a++) {
                        Node actionNode = actionsList.item(a);
                        if (actionNode instanceof Element) {
                            Element actionEl = (Element)actionNode;
                            String className="";
                            if(actionEl.getTagName().equals("action"))
                            {
                                NamedNodeMap attributes = actionEl.getAttributes();
                                for(int aa=0; aa<attributes.getLength(); aa++)
                                {
                                    Node attr = attributes.item(aa);

                                    if (attr.getNodeName().equals("class_name"))
                                    {
                                        className = attr.getNodeValue();
                                        if(className.equals(ActivateTorquesAction.class.getName()))
                                            StaticActions.addAction( new ActivateTorquesAction(actionEl, getEngine().getTorqueList()));
                                        else if(className.equals(DeactivateTorquesAction.class.getName()))
                                            StaticActions.addAction( new DeactivateTorquesAction(actionEl, getEngine().getTorqueList()));
                                        else if(className.equals(RunJavaScriptCodeAction.class.getName()))
                                            StaticActions.addAction( new RunJavaScriptCodeAction(actionEl) );
                                    }
                                }
                            }
                        }
                    }
                }
                else if (elem.getTagName().equals("Virtual_Keyboard")) {
                    XMLControlCenterHelper.loadVirtualKeyboard(elem);
                }
                else if (elem.getTagName().equals("scripts")) {
                    XMLControlCenterHelper.loadScripts(elem);
                }
            }
        }

        
//        if (centerView && actualSpringBody!=null)
//            GC.getGraphicPanel().centerViewToVec(actualSpringBody.getCenterVec());
        

        GUIC.repaintAllSceneTabs();
        */
    //}

    /**
     * Returns index of Action Frame side box pointed by vec.
     *
     * @param vec where to look for side boxes
     * @return index of pointed side box.
     *         -1 - none
     *          1 - Top Left
     *          2 - Top Middle
     *          3 - Top Right
     *          4 - Right Middle
     *          5 - Bottom Right
     *          6 - Bottom Middle
     *          7 - Bottom Left
     *          8 - Left Middle
     */
    public int getActionFrameSideBoxIndexAt(Vec2d vec, Scene scene)
    {
        if (scene.EC.currentBody==null)
            return -1;
        
        for(int i=1; i<9; i++)
        {
            Rectangle2D rect = scene.GC.getGraphicPanel().getActionFrameSideBoxRect(scene.EC.currentBody, i);
            if (rect.contains(vec.getX(), vec.getY()))
                return i;
        }

        return -1;
    }

////////////////////////////////////////////////////////////////////////////////
//                        P R O J E C T   T R E E                             //
////////////////////////////////////////////////////////////////////////////////

    public ProjectTreeNode projectTreeRoot = new ProjectNode();
    
    public ScenesTreeNode scenesTreeNode = new ScenesTreeNode();

    class ProjectNode implements ProjectTreeNode {
        
        public ProjectTreeNode getChild(int index) {
            switch(index) {
                case 0:
                    return scenesTreeNode;
            }

            return null;
        }
        
        public int getChildCount() {
            return 1;
        }

        public boolean isLeaf() {
            return false;
        }

        public int getIndexOfChild(Object child) {
            if (child instanceof ScenesTreeNode)
                return 0;
            
            else
                return -1;
        }

        public void resetNodeValue(Object o) {}

        public String toString() {
            return "Project";
        }
    }

    // Root node for scenes filters
    class ScenesTreeNode extends ProjectTreeSceneFilterNode {
        public ScenesTreeNode() {
            super();
            this.setFilterName("Scenes");
        }
    }

}
